﻿#define _CRT_SECURE_NO_WARNINGS

#include <chrono>
#include <cmath>
#include <iostream>
#include <sstream>

#include "MLProgressbar.h"

using namespace std;
using namespace std::chrono;

MLProgressbar::MLProgressbar(const size_t total, const char* description) {
	_progress = 0;
	_total = total;
	_description = description;
	auto now = system_clock::now();
	_start_time = duration_cast<microseconds>(now.time_since_epoch()).count();
	_last_update_time = _start_time;
	printf("#%s: 0%% | 0/%zu [00:00:00<?, ?it/s]\n", _description, _total);
	for (int i = 0; i < _n_records; i++) {
		_records.push_back(0);
	}
}

void MLProgressbar::update() {
	_progress++;
	_output();
	if (_progress == _total) printf("\n\n\n");
}

string MLProgressbar::_format_time(size_t time_us) {
	size_t time_hours = time_us / 3600000000;
	size_t time_minutes = (time_us - time_hours * 3600000000) / 60000000;
	size_t time_seconds = (time_us - time_hours * 3600000000 - time_minutes * 60000000) / 1000000;
	char* time_str = new char[16];
	sprintf(time_str, "%zu:%02zu:%02zu", time_hours, time_minutes, time_seconds);
	auto time_cpp_str = std::string(time_str);
	delete[] time_str;
	return time_cpp_str;
}

void MLProgressbar::_output() {
	system_clock::time_point now = system_clock::now();
	size_t current_time_us = duration_cast<microseconds>(now.time_since_epoch()).count();
	size_t update_time_us = current_time_us - _last_update_time;
	size_t index = _progress % _n_records;
	_eta_update_time_us = _eta_update_time_us + (int64_t)(update_time_us - _records[index]) / _n_records;
	_records[index] = update_time_us;
	size_t passed_time_us = current_time_us - _start_time;
	size_t eta_us = _eta_update_time_us * (_total - _progress);
	double iter_per_sec;
	if (_eta_update_time_us != 0) iter_per_sec = 1e+6 / _eta_update_time_us;
	else iter_per_sec = 0;
	double finished_rate = 100.0 * _progress / _total;
	string eta_str = _format_time(eta_us);
	string passed_time_str = _format_time(passed_time_us);
	printf("#%s: %.2f%% | %zu/%zu [%s<%s, %.2fit/s]\n",
		_description, round(finished_rate * 100) / 100, 
		_progress, _total, passed_time_str.c_str(), eta_str.c_str(), round(iter_per_sec * 100) / 100);
	_last_update_time = current_time_us;
}
